﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace tactics
{
    public class HeightMap
    {
        float[] heightMap;
        float[] noise;

        int seed;
        int width;
        int height;
        float scale;
        float minHeight;
        float maxHeight;
        float steepness;

        bool noiseInitialized;

        #region Properties

        public int Width
        {
            get
            {
                return width;
            }
        }

        public int Height
        {
            get
            {
                return height;
            }
        }

        public int Length
        {
            get
            {
                return heightMap.Length;
            }
        }

        public float Scale
        {
            get
            {
                return scale;
            }
        }

        public float MinimumHeight
        {
            get
            {
                return minHeight;
            }
        }

        public float MaximumHeight
        {
            get
            {
                return maxHeight;
            }
            set
            {
                maxHeight = value;
            }
        }

        public float Steepness
        {
            get
            {
                return steepness;
            }
        }

        public int Seed
        {
            get
            {
                return seed;
            }
            set
            {
                seed = value;
            }
        }

        #endregion

        public HeightMap(int widthIn, int heightIn, float scaleIn, float minHeightIn, float maxHeightIn, float steepnessIn, int seedIn)
        {
            width = widthIn;
            height = heightIn;
            scale = scaleIn;
            minHeight = minHeightIn;
            maxHeight = maxHeightIn;
            steepness = steepnessIn;

            heightMap = new float[width * height];

            for (int i = 0; i < heightMap.Length; ++i)
                heightMap[i] = 0.0f;                

            seed = (seedIn == 0) ? new Random().Next() : seedIn;
        }

        // helper function, lets us use x/y instead of array index
        public int AtCoord(int x, int y)
        {
            if ((x >= 0 && y >= 0) && (x < width  && y < height))
                return y * width + x;
            else
                return -1;
        }
        
        public float GetHeight(int x, int y)
        {
            return heightMap[AtCoord(x, y)];
        }

        public void SetHeight(int x, int y, float z)
        {
            heightMap[AtCoord(x, y)] = z;
        }

        //Gets the value for a specific X and Y coordinate
        private float GetRandomHeight(float x, float y, float maxHeight, float frequency, float amplitude, float persistance, int octaves)
        {
            GenerateNoise();
            float finalValue = 0.0f;
            for (int i = 0; i < octaves; ++i)
            {
                finalValue += GetSmoothNoise(x * frequency, y * frequency) * amplitude;
                frequency *= 2.0f;
                amplitude *= persistance;
            }
            if (finalValue < -1.0f)
            {
                finalValue = -1.0f;
            }
            else if (finalValue > 1.0f)
            {
                finalValue = 1.0f;
            }
            return finalValue * maxHeight;
        }
        
        // bilinear filtering
        private float GetSmoothNoise(float x, float y)
        {
            float fractionX = x - (int)x;
            float fractionY = y - (int)y;
            
            int x1 = ((int)x + width) % width;
            int y1 = ((int)y + height) % height;
            
            int x2 = ((int)x + width - 1) % width;
            int y2 = ((int)y + height - 1) % height;
            
            float finalValue = 0.0f;
            
            finalValue += fractionX * fractionY * noise[AtCoord(x1, y1)];
            finalValue += fractionX * (1 - fractionY) * noise[AtCoord(x1, y2)];
            finalValue += (1 - fractionX) * fractionY * noise[AtCoord(x2, y1)];
            finalValue += (1 - fractionX) * (1 - fractionY) * noise[AtCoord(x2, y2)];

            return finalValue;
        }

        private void GenerateNoise()
        {
            if (noiseInitialized)
                return;

            noise = new float[heightMap.Length];           

            Random rand = new Random(seed);
            
            for (int i = 0; i < Length; ++i)
            {
                noise[i] = ((float)(rand.NextDouble()) - 0.5f) * 2.0f;  //Generate noise between -1 and 1
            }

            noiseInitialized = true;
        }

        public void GeneratePerlinHeightMap()
        {
            noiseInitialized = false;
            int length = width * height;
            heightMap = new float[length];

            for (int x = 0; x < width; ++x)
            {
                for (int y = 0; y < height; ++y)
                {
                    heightMap[AtCoord(x, y)] = GetRandomHeight(x, y, maxHeight, 0.04f, 0.1f, 0.5f, 12);
                }
            }
        }

        // creates a basic, totally random heightmap with a small set of constraints
        public void GenerateRandomHeightMap()
        {
            Random rand = new Random();
            
            heightMap = new float[width * height];

            for (int y = 0; y < height; ++y)
                for (int x = 0; x < width; ++x)
                    heightMap[AtCoord(x, y)] = ((float)rand.NextDouble() * maxHeight);
        }
    }
}
